Terraform Part-2
Resource Dependencies
resource "aws_instance" "example" {
ami = "ami-2757f631"
instance_type = "t2.micro"
}
resource "aws_eip" "ip" {
vpc = true
instance = aws_instance.example.id
}Bir resource un diğer resource dan önce oluşturulması gerekebilir.
Implicit and Explicit Dependencies:
Yukarıdaki örnekte implicit bir bağlılık var kaynakların bağımlılığını terraform kendisi çıkartır. Önce instance daha sonra eip oluşturulur.
Örneğin EC2 içindeki bir uygulama S3 bucket ını kullanacaksa bunu explicit bağımlılık olarak depends_on argümanı ile belirtmeliyiz.
resource "aws_s3_bucket" "example" {
bucket = "clarusway-terraform-guide"
acl = "private"
}
resource "aws_instance" "example" {
ami = "ami-2757f631"
instance_type = "t2.micro"
depends_on = [aws_s3_bucket.example]
}Mutable && Immutable Infrastructure
Kaynaklarda değişiklik veya güncelleme yapmanın 2 yolu vardır. Birincisi mutable yani kaynak aynı kalır sadece değişiklik yaparız. İkincisi ise immutable yani yeni istenen özelliklerle yeni bir kaynak oluşturulur ve eskisi destroy edilir. Terraform güncellemeleri immutable olarak gerçekleştirir. Bu işlemi yaparken de önce eski kaynağı destroy eder daha sonra yenisini oluşturur.
Bu yaklaşım kullanıma göre sıkıntılar oluşturabilir. Yani önce yenisi oluşturulsun eskisi daha sonra silinsin isteyebilirsiniz. Bu durumda resource bloğu içine lifecycle bloğu oluşturmak gerekmektedir. Bu blokla ayrıca istenmeden database gibi kaynakların destroy edilmesini de önleyebiliyoruz. Aşağıda örneğini görebilirsiniz.
lifecycle {
create_before_destroy = true
}lifecycle {
prevent_destroy = true
}Data Source
Provider’lar tarafından hazırlanan resource oluştururken kullanılan bloklardır. Offical siteyi incelediğimizde her kaynağın altında onunla ilgili data blokları da yer alır. Amazon tarafından yayınlanan ubuntu imajı ihtiyacımız olduğunu varsayalım. Bu bilgiyi çekmek için data bloklarını kullanabiliyoruz. Aşağıdaki örnekte data bloğunu görebilirsiniz. Dikkat ederseniz filter bloklarıyla kısıtlamalar yapabiliyoruz.

Daha sonra bu bilgiyi ec2 oluştururken aşağıdaki gibi kullanacağız.

Hesabımızda kendi oluşturduğumuz imajımız olsaydı ve bunu kullanmak istese idik bu durumda owner kısmına [”self”] yazmamız gerekecekti.
Aşağıda tablodan resource ve data source arasındaki farkı inceleyebilirsiniz.

Remote State
Öncelikle bu özelliğin ne işe yaradığından bahsedelim. Daha önce öğrenmiştik terraform oluşturduğu kaynakları state file’ında tutar. Bir değişiklik yapıldığında state file da değişir. Bir grubunuz var aynı hesap üzerinde çalıştığınızı varsayalım. Birinci kişi 2 adet ec2 3 tane de bucket oluşturdu. Bu kişinin çalıştığı bilgisayarda state file’ında bu kaynaklar oluştu. İkinci kişi de 1 ec2 ve 2 tane de bucket oluşturmak istedi ve kodu çalıştırdı. Sonuç ne olacak? En son çalıştıran kişinin state file’ında da 1 ec2 ve 2 bucket olacak. Toplamda 2 ec2 ve 5 bucket’ımız oldu ama bunlar farklı state file’larda tutuluyor. Bu sıkıntıyı gidermek için yapılmış bir çözümdür remote state.
Burada bir backend tanımlıyoruz. AWS S3 veya terraform Cloud backend örnekleridir. Bu şekilde çalıştığımızda state file’lar S3 gibi uzakta bir yerde muhafaza edilir. Her değişikliği yapan işlemi bitince burdaki state file’a son durum yazılmış olur ve herkes aynı durumu görmüş olur. Bir detay daha var aslında .tf dosyaları da herkeste aynı olması gerekiyor bunun için de github gibi VCS kullanılabilir. Aşağıdaki resim çok güzel özetliyor aslında.

Bu özelliği kullanmak için bir s3 bucket ve bir de dynamodb tablosuna ihtiyacımız var. S3 bucket’ı state file ı tutmak için kullanacağız. Şifreleme ve versiyonlama özelliğini kullanacağız. Dynamodb tablosunu ise lock özelliği için kullanacağız. Eğer aynı anda 2 farklı kişi işlem yapmaya çalışırsa problem çıkacaktır. Bunu önlemek için bir kişi işlem başlattığında diğerlerinin ulaşmasını önlemek için dosya kilitlenir.
Öncelikle farklı bir klasörde backend.tf dosyası oluşturalım. İçeriği aşağıdaki gibi olacak bir adet s3 bucket ve dynamodb tablosu oluşturmuş olacağız.
resource "aws_s3_bucket" "remote-state" {
bucket = "bucket-remote-state"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
resource "aws_dynamodb_table" "tf-remote-state-lock" {
hash_key = "LockID"
name = "tf-s3-app-lock"
attribute {
name = "LockID"
type = "S"
}
}S3 bucket için versiyonlama özeliğini açtık ve AES256 şifrelemeyi aktif ettik. Dynamodb de ise hash_key = "LockID" özelliği ile aynı anda birden fazla erişim olmaması için kilitleme özelliğini kullandık. Kaynakları oluşturduktan sonra terraform init ve terraform apply komutlarını uygulayalım.
Daha sonra kendi çalıştığımız klasöre dönelim ve main.tf dosyamızda terraform bloğu içerisine aşağıdaki kodu yapıştıralım.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.70.0"
}
}
backend "s3" {
bucket = "tf-remote-s3-bucket-oliver-changehere"
key = "env/dev/tf-remote-backend.tfstate"
region = "us-east-1"
dynamodb_table = "tf-s3-app-lock"
encrypt = true
}
}Bu işlemden sonra terraform init komutu tekrar çalıştırılmalıdır. state file’a baktığınızda boş oluğunu göreceksiniz. Artık State file’lar s3 içerisinde env/dev/ klasörü altında olmuş olacak.
Provisioners
AWS servisi olan EC2 ayağa kaldırırken user data girme özelliği vardı. Daha EC2 ayağa kalkarken script yazarak bazı programların kurulmasını, dosyaların oluşturulmasını sağlayabiliyorduk. Provisioner da aslında bu işi yapıyor kaynak oluşurken aşağıda göstereceğim şekilde bir connection kurup security grubu ayarladıktan sonra bazı işlemleri yaptırabiliyoruz. Bu işlemler kaynakta olacaksa remote-exec provisioner, eğer çalıştığımız klasörde olacaksa da local-exec provisioner diyoruz. ‘ şart vardı tekrar hatırlatayım security grubun ssh portuna izin vermesi ve connection bloğunun hazırlanması. Diğer bir husus provisioner ve connection bloğunun resource bloğu içinde olması gerekiyor.

İlk büyük bloğu biliyoruz instance oluşturma bloğu ve sayfanın sonunda bitiyor. İkinci blok remote-exec provisioner bloğu. Birden fazla işlem olduğu için dizi içerisine aralara ”,” koyarak apache web server kurup başlatıyoruz ve HTML klasörü içine bir yazı yazdıran index.html atıyoruz.
Son blok connection bloğu. Dikkat ederseniz ssh bağlantısında kullandığımız parametreleri giriyoruz. “ssh -i key.pem ec2-user@public_ip “ buradaki değerleri girmiş oluyoruz. Security grubu da yandaki gibi ayarladıktan sonra apply dediğimizde public_ip ile istediğimiz sonucu web browser’dan görebiliriz.
Şimdi de local-exec ten basedelim. local-exec provisioner, remote-exec in aksine local terraform binary dosyalarının olduğu makinede işlem yapar. EC2 oluştuktan sonra atanan public ve private ip’yi çalıştığımız makinede bir txt file’ında tutmak istiyoruz. Bu durumda aşağıdaki gibi local exec kullanmamız gerekiyor.

Son olarak provisioner’larda behavior belirtebiliyoruz. Yani destroy edildiğinde arklı komut çalıştırmasını istiyorsak;

veya bir hata olması durumunda görmezden gel demek istersek aşağıdaki gibi behavior girmemiz gerekir. temp klasörü olmadığı için bu hata almayı bekleriz.

Tam burada bir detay daha vermem gerekecek. Eğer on_failure = continue demesek bile Terraform aslında kaynağı oluşturur sadece local-exec’i uygulamaz. Bu durumda terraform bu kaynağı taint yani lekeli kabul eder ve bir sonraki apply da bu kaynağı destroy edip tekrar kurar. Bu özelliği biz kullanmak da isteyebiliriz. Diyelim yeni versiyon kurmak istedik biz remote-exec bloğunu değiştirsek de bir değişiklik görmeyecek. Ama aşağıdaki komutla kaynağı lekeli ilan edip sonraki apply da kaynağı destroy edip daha sonra create etmesini isteyebiliriz.
terraform taint aws_instance.ec2
terraform untaint aws_instance.ec2Bu kadar anlattık ama terraform provisioner kullanmayı son çare olarak kullanın der. Herhangi yetki hatası veya bağlantı hatası durumunda işleminiz gerçekleşmeyecektir. Bunun yerine user data kullanılması daha sağlıklı olacak. AWS deki user data’nın diğer provider’larda da karşılığı mevcuttur.

Aslında en sağlıklısı da nginx yüklü bir ami seçip o şekilde instance ayağa kaldırmaktır.
Import
Şimdiye kadar kaynakları terraform kullanarak oluşturduk daha sonra kaldırdık gibi bir takım işlemler yaptık. Ya dışarıda bir kaynağınız daha var bunu da terraform ile kontrol etmek istersek ne yapacağız? Aşağıdaki resimde 3 farklı yöntemle oluşturulmuş resource’ları terraform içine almak istiyoruz. Terraform bu kaynakları import etme şansı veriyor bize.

Öncelikle .tf dosyamıza o kaynağı boş da olsa oluşturmamız gerekiyor. İsterseniz bilinen özeliklerini instance_type, ami vs girebiliriz. Daha sonra da import komutunu çalıştırdığımızda sanki apply yapmış gibi resource terraform tarafından kontrol edilebilir oluyor.
Şimdi AWS konsol’dan bir EC2 oluşturalım ve instance id’sini kopyalayalım. Daha sonra çalıştığımız klasörde bir aws_instance kaynağı oluşturalım, terminalden de import komutumuzu yazalım.

İçeri aldık ama işlem bitmiyor. Kendisi state file oluşturdu ve tüm özellikleri çekildi. Biz bazı bilgileri main.tf’e eklememiz gerekecek. Aşağıdaki gibi bilgileri eklediğimizde sonrasında plan komutunu çalıştırdığımızda no changes yani değişiklik yok almış olacağız. Burada main.tf dosyasını manual olarak hazırlamak zorunda kalıyoruz. Terraform’da bunun farkında ve bu konuda çalıştığını dokümanda belirtmiş.

Burada şöyle bir soru gelebilir. EC2’nun dünya kadar özelliği var ben hangilerini girsem yeterli olur? Eğer bilmiyorsak terraform show komutunu çalıştıralım ve çıkan sonucu main.tf e yapıştıralım garanti olmuş olur.
Modules
Bu da son konumuz olmuş olacak. Sürekli farklı birimler için aynı resource’lara ihtiyacımız olacaksa burada module devreye giriyor. Bir templete hazırlıyoruz gibi düşünebiliriz daha sonra hazırladığımız templete’in yolunu göstererek aynı cins yeni kaynakları çok kolay şekilde oluşturabiliyoruz. Temel mantığı anladıysak bir örnek yapalım. Aşağıdaki yapıyı farklı regionlarda kurmamız isterse bunu module ile kolayca yapabiliriz. Bir instance, bir s3 Bucket ve bir de dynamodb table’a ihtiyacımız var.

Önce tüm bu resource’ların oluşturulduğu bir modules klasörü içinde .tf dosyalarımızı oluşturalım.

depends_on ile instance’ın S3 bucket ve dynamodb_table’dan sonra oluşturulmasını sağlıyoruz. Diğer kısımlar bu zamana kadar yaptığımız resource oluşturma kısmı. Bu yapıyı bir defa kurduktan sonra aşağıdaki module yapısını kullanarak istediğimiz kadar farklı yerde kullanabileceğiz.

module bloğu oluşturduk. Source olarak bir önceki klasörde bulunan daha önce yukarıda yaptığımız dosyaları gösterdik. variable’ları da tanımladık. Bundan sonra yapacağımız terminaden ilgili klasöre gelip terraform init ve terraform apply komutu vermek.
Çok önemli görmediğim kavram olarak da root child module kavramlarını ayıralım. En son hazırladığımız module blogu alan klasörümüz root module, özellikler aldığımız yani kaynakları oluşturduğumuz kısım ise child module olmuş oluyor. Programlama dillerinde olan package’lere benzetilebilir. Child module de terraform komutları çalıştırılmasına gerek yoktur. Root module de komutlar çalıştığında gerekli kaynak bilgilerini child module’den almış olacak.
Biz bu işlemi local’de hazırladık ve kullandık. Ayrıca offical sitede bulunan daha önce hazırlanan module’leri de kullanabiliriz.
module "security-group" {
source = "terraform-aws-modules/security-group/aws"
version = "4.7.0"
# insert the 3 required variables here
}Yukarıdaki .tf dosyasını kaydettikten sonra terraform get komutunu çalıştırdığımızda aynı klasörde .terraform klasörü altında içeriği görebiliriz.

